<script setup>
import {
  onMounted,
  toRefs,
  nextTick,
  watch,
  ref,
  getCurrentInstance,
  computed
} from 'vue'
import { addTokenToUrl } from '../../../utils/token'
import { uploadImg } from '../../../service/api'
import {
  jumpHttp,
  isIos,
  getAllSrc,
  isMobile,
  isWeixinMini
} from '../../../utils/host'
import throttle from "lodash/throttle";
import userDefault from '../../../assets/img/user.png'
import botDefault from '../../../assets/img/school.png'

const { appContext } = getCurrentInstance()
const proxy = appContext.config.globalProperties
const config = proxy.$config

const props = defineProps({
  type: {
    type: String, // User(发送的问题) || Bot(答案) || Kefu(人工客服) || PreAbnormal(前置异常)
    default: 'Text'
  },
  stype: {
    type: Number,
    default: 0
  },
  i: {
    type: Number,
    default: 0
  }, // 消息索引
  name: {
    type: String,
    default: ''
  },
  answer: {
    type: String,
    default: ''
  },
  message: {
    type: String,
    default: ''
  },
  isLoading: {
    type: Boolean,
    default: false
  },
  showInfo: {
    type: Boolean,
    default: false
  },
  quote: {
    type: Array,
    default: () => {
      return []
    }
  },
  isTextEnd: {
    type: Boolean,
    default: false
  },
  isActive: {
    type: Boolean
  },
  isAnswering: {
    type: Boolean
  },
  uuid: {
    type: String,
    default: ''
  },
  needUploadImg: {
    type: Boolean
  },
  list: {
    type: Array,
    default: () => {
      return []
    }
  },
  table: {
    type: Object,
    default: () => {
      return {}
    }
  },
  btns: {
    type: Array,
    default: () => {
      return []
    }
  },
  hrefList: {
    type: Array,
    default: () => {
      return []
    }
  },
  opsStatus: {
    type: String,
    default: '0'
  },
  chatType: {
    type: String,
    default: ''
  },
  src: {
    type: String,
    default: ''
  },
  muted: {
    type: Boolean
  },
  isError: {
    type: Boolean,
    default: false
  },
  actionType: {
    type: String,
    default: 'normal'
  },
  isHistory: {
    type: Boolean
  },
  total: {
    type: Number,
    default: 0
  }
})

const emits = defineEmits([
  'emitSend',
  'emitScroll',
  'refresh',
  'emitKefu',
  'emitRef',
  'emitImg',
  'stopAi',
  'emitEvalute',
  'emitStatus',
  'emitPlay',
  'emitPause',
  'emitEnd'
])

const {
  i,
  name,
  type,
  stype,
  answer,
  message,
  isLoading,
  showInfo,
  quote,
  isTextEnd,
  isActive,
  isAnswering,
  uuid,
  needUploadImg,
  list,
  table,
  btns,
  hrefList,
  opsStatus,
  chatType,
  src,
  muted,
  isError,
  actionType,
  isHistory
} = toRefs(props)
// 是否为最后一条message
const isLast = computed(() => props.i === props.total - 1)
// 引用切片里面的图片
const imgs = ref([])

// const rawHtml = ref("");
// let lastEncoded = ""; // 缓存“转义字符串”上次完整值

// // 工具函数：HTML解码
// function decodeHtml(html) {
//   const txt = document.createElement("textarea");
//   txt.innerHTML = html;
//   return txt.value;
// }

// watch(
//   message,
//   throttle((newVal) => {
//     if (!newVal || newVal === lastEncoded) return;

//     // 找出转义字符串上的差异
//     const diffEncoded = newVal.slice(lastEncoded.length);
//     lastEncoded = newVal;

//     // 解码差异部分
//     const decodedDiff = decodeHtml(diffEncoded);  // 只解码一次

//     // 拼接到最终的 HTML 内容
//     rawHtml.value += decodedDiff;

//     console.log(">>> newVal:", newVal);
//     console.log(">>> decoded diff:", decodedDiff);
//     console.log(">>> final rawHtml:", rawHtml.value);
//   }, 300)
// );

// const rawHtml = computed(() => {
//   // console.log('****** rawHtml ******', message.value)
//   const txt = document.createElement("textarea");
//   txt.innerHTML = message.value;
//   // console.log('****** rawHtml txt.value ******', txt.value)
//   return txt.value;
// });

const rawHtml = ref("");  // 保存最终的 HTML 字符串

// 定义节流处理函数
const updateRawHtml = throttle(() => {
  const txt = document.createElement("textarea");
  txt.innerHTML = message.value;
  rawHtml.value = txt.value;
}, 300); // 300ms 为节流间隔，可以根据需要调整

// 在消息值更新时，调用节流函数
watch(message, updateRawHtml);

// rawHtml 计算属性，用于获取解析后的 HTML
const computedRawHtml = computed(() => rawHtml.value);

const go = (item) => {
  if (!item.link) {
    proxy.$toast('当前服务正在和学校建设中，请耐心等待吧')
    return
  }
  if (item.type === 4) {
    // 跳转app应用 wanxiao://......
    if (!isMobile()) {
      proxy.$toast('请在移动端使用此功能')
      return
    }
    window.location.href = item.link
  } else if (item.type === 2) {
    if (!isWeixinMini()) {
      proxy.$toast('请在微信小程序环境使用此功能')
      return
    }
    wx.miniProgram.navigateTo({
      url: item.link
    })
  } else if (item.type === 3) {
    // 跳转http链接
    const url =
      item.appendToken === 1
        ? addTokenToUrl(
            item.link,
            item.putTokenName || 'token',
            item.gainTokenName || 'token'
          )
        : item.link
    if (url === 'invalid url') {
      proxy.$toast('地址解析失败，请检查配置')
      return
    }
    jumpHttp(url)
  }
}

const stopAi = () => {
  emits('stopAi', true)
}

// 是否解决
const solve = (status) => {
  if (status === opsStatus.value) return
  emits('emitEvalute', {
    question: answer.value,
    ask: name.value,
    index: i.value,
    uuid: uuid.value,
    status,
    chatType: chatType.value
  })
}

watch(type, () => {
  if (type.value !== 'Bot') {
    isTextEnd.value = true
  }
  nextTick(() => {
    emits('emitScroll')
  })
})

watch(quote, (v) => {
  if (v) {
    v.forEach((item) => {
      // item.ref_content = `<p>${item.ref_content}</p>`
      imgs.value = imgs.value.concat(getAllSrc(item.ref_content))
    })
    imgs.value.forEach((src) => {
      const img = new Image()
      img.src = src
      img.onload = () => {
        emits('emitScroll')
      }
    })
  }
})

watch(src, (v) => {
  if (v) {
    setTimeout(() => {
      voice.value.addEventListener('ended', () => {
        isPlay.value = false
        emits('emitEnd')
      })
    }, 50)
  }
})

// 点击查看文档引用
const showRef = (item) => {
  emits(
    'emitRef',
    item.ref_content
      .replace(/--------------------/g, '\n')
      .replace(/知识点：\n/g, '')
  )
}

// 点击预览图片
const showImage = (index) => {
  emits('emitImg', {
    imgs: imgs.value,
    index
  })
}

// 人工客服
const kefu = () => {
  setTimeout(() => {
    const url = config.manServiceToken
      ? addTokenToUrl(config.manServiceUrl, 'token', 'token')
      : config.manServiceUrl
    if (url === 'invalid url') {
      proxy.$toast('地址解析失败，请检查配置')
      return
    }
    jumpHttp(url)
  }, 50)
}

// 富文本转成文本，去掉html标签
const getSimpleText = (html) => {
  const re1 = new RegExp('<.+?>', 'g') // 匹配html标签的正则表达式，"g"是搜索匹配多个符合的内容
  const arrEntities = {
    lt: '<',
    gt: '>',
    nbsp: ' ',
    amp: '&',
    quot: '"',
    ldquo: '“',
    mdash: '—',
    rdquo: '”'
  }
  return html
    .replace(re1, '')
    .replace(/&(lt|gt|nbsp|amp|quot|ldquo|mdash|rdquo);/gi, function (all, t) {
      return arrEntities[t]
    })
}

const copy = () => {
  const textArea = document.createElement('textArea')
  textArea.value = getSimpleText(message.value)
  textArea.style.width = 0
  textArea.style.position = 'fixed'
  textArea.style.left = '-999px'
  textArea.style.top = '10px'
  textArea.setAttribute('readonly', 'readonly')
  document.body.appendChild(textArea)

  textArea.select()
  document.execCommand('copy')
  document.body.removeChild(textArea)

  proxy.$toast('已复制')
}

const handleClick = (name) => {
  // 4: 发送用户点击的按钮文本
  emits('emitSend', {
    type: 4,
    data: {
      name
    }
  })
}

// 上传图片
const showAddBtn = ref(true)
const upload = ref()
const uploadImgArr = ref([])
let uploadImgs = ''
const imageChange = () => {
  const l = upload.value.files.length
  proxy.$toast.loading({
    message: '上传中...',
    duration: 0,
    forbidClick: true
  })
  for (let i = 0; i < l; i++) {
    const file = upload.value.files[i]
    uploadImg({
      file
    }).then((res) => {
      showAddBtn.value = false
      uploadImgArr.value.push(res.link)
      if (uploadImgArr.value.length === l) {
        proxy.$toast.clear()
        uploadImgs = uploadImgArr.value.join(',')
        // 3: 发送用户上传图片
        setTimeout(() => {
          emits('emitSend', {
            type: 3,
            data: {
              name: `图片列表: ${uploadImgs}`
            }
          })
        }, 1000)
      }
    })
  }
}

// a标签跳转、img预览   stype = 7 stype = 5
const jumpA = (e) => {
  e.preventDefault()
  if (stype.value === 7) {
    if (e.target.nodeName === 'A') {
      const index = e.target.getAttribute('href')
      if (index) {
        window.location.href = hrefList.value[index]
      }
    }
  } else {
    if (e.target.nodeName === 'A') {
      const url = e.target.getAttribute('href')
      jumpHttp(url)
    }
  }

  if (e.target.nodeName === 'IMG') {
    const src = e.target.getAttribute('src')
    if (src) {
      emits('emitImg', {
        imgs: [src],
        index: 0
      })
    }
  }
}

const voice = ref()
const isPlay = ref(false)
const playAnswer = () => {
  if (voice.value.paused) {
    voice.value.play()
    emits('emitPlay', actionType.value)
    isPlay.value = true
  } else {
    voice.value.pause()
    emits('emitPause')
    isPlay.value = false
  }
}
onMounted(() => {})
</script>

<template>
  <div class="message-wrap bot" v-if="type === 'Bot'">
    <audio v-show="isIos() && src" ref="voice" :src="src" :muted="muted" />
    <div class="AI">
      <img class="avatar" :src="config.botIcon || botDefault" />
      <div class="loadwrap" v-if="!showInfo">
        <span>AI正在{{ isLoading ? '思考' : '生成' }}中</span>
        <div class="loader">
          <div class="box-load1" />
          <div class="box-load2" />
          <div class="box-load3" />
        </div>
      </div>
      <span v-else>以下内容由AI生成</span>
    </div>
    <div class="message" v-if="message">
      <!-- <span class="rich-text">{{ message }}<span v-if="!showInfo" class="flashing-line" /></span> -->

      <span
        v-if="stype == 7 || stype == 5 || stype == 4"
        class="rich-text"
        v-html="computedRawHtml"
        @click="jumpA($event)"
      />
      <span v-else class="rich-text" v-html="computedRawHtml" />

      <div v-if="btns.length && showInfo" class="btns">
        <div
          class="btn oneline"
          v-for="(item, index) in btns"
          :key="index"
          @click="handleClick(item)"
        >
          {{ item }}
          <span class="next" />
        </div>
      </div>

      <div class="table" v-if="table.tr && showInfo">
        <table>
          <thead>
            <tr v-if="table.th.length">
              <th v-for="(item, index) in table.th" :key="index">
                {{ item }}
              </th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(l, c) in table.tr" :key="c">
              <td v-for="(m, n) in l" :key="n">
                {{ m }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <div v-if="list.length && showInfo" class="funcs">
        <div class="suggest">为您推荐以下应用</div>
        <div
          class="func"
          v-for="(item, index) in list"
          :key="index"
          @click="go(item)"
        >
          <img v-if="item.icon" :src="item.icon" />
          <span class="title oneline"
            >{{ item.name }} <span class="next"
          /></span>
        </div>
      </div>

      <div class="quote" v-if="quote.length && showInfo">
        <div class="refs">
          <span class="t">找到了以下{{ quote.length }}篇资料作为参考</span>
          <span
            v-for="(item, index) in quote"
            :key="index"
            @click="showRef(item)"
            >{{ item.ref_name }}</span
          >
        </div>
      </div>

      <div class="upload" v-if="needUploadImg">
        <div class="list" v-if="uploadImgArr.length">
          <img
            v-for="(item, index) in uploadImgArr"
            :key="index"
            class="img"
            :src="item"
          />
        </div>
        <div class="upload-wrap" v-if="showAddBtn">
          <input
            ref="upload"
            type="file"
            multiple
            accept="image/*"
            @change="imageChange"
          />
        </div>
      </div>

      <div class="tips" v-if="showInfo && stype === 1">
        以上内容由AI生成，仅供参考
      </div>
      <div class="tips" v-if="showInfo && stype === 4">
        以上信息由AI大模型依据校园公开资讯生成，内容仅供参考
      </div>
      <div class="tips" v-if="showInfo && stype === 5">
        以上信息由AI大模型依据校园公开资讯生成，内容仅供参考
      </div>

      <div class="bottom">
        <div
          class="MP3_btn"
          v-if="showInfo && isIos() && !isError && !isHistory"
          :class="{ play: !isPlay, pause: isPlay }"
          @click="playAnswer"
        />
        <div class="ops">
          <div
            @click="stopAi"
            style="margin-right: 30px"
            v-if="!showInfo"
          >
            停止
          </div>
          <div v-if="showInfo" @click="copy" style="margin-right: 30px">
            复制
          </div>
          <div @click="solve('1')" v-if="isLast" style="margin-right: 30px">
            <img
              v-if="opsStatus === '0' || opsStatus === '2'"
              src="../../../assets/img/good2.png"
            />
            <img
              class="like"
              v-if="opsStatus === '1'"
              src="../../../assets/img/good1.png"
            />
            <span :class="{ active: opsStatus === '1' }">点赞</span>
          </div>
          <div @click="solve('2')" v-if="isLast">
            <img
              v-if="opsStatus === '0' || opsStatus === '1'"
              src="../../../assets/img/nogood2.png"
            />
            <img
              class="unlike"
              v-if="opsStatus === '2'"
              src="../../../assets/img/nogood1.png"
            />
            <span :class="{ active: opsStatus === '2' }">踩</span>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="message-wrap user" v-if="type === 'User'">
    <img class="avatar" :src="config.userAvatar || userDefault" />
    <div class="message">
      <span class="rich-text" v-html="message" />
    </div>
  </div>
  <div class="message-wrap preabnormal" v-if="type === 'PreAbnormal'">
    <div class="message">
      <div class="ops">
        <span class="tip" />
      </div>
      {{ message }}
    </div>
  </div>
  <div class="message-wrap kefu" v-if="type === 'Kefu'" @click="kefu">
    <span>问题未解决，我要转</span><span class="active">人工客服</span>
  </div>
</template>

<style lang="less">
.message-wrap {
  margin-bottom: 20px;
  display: flex;
  font-size: 16px;
  color: #333;
  .active {
    color: #f59f49;
  }
  .message {
    background-color: #ffffff;
    border-radius: 8px;
    max-width: 100%;
    word-wrap: break-word;
    // white-space: pre-wrap;
    line-height: 1.4;
  }
  .rich-text {
    width: 100%;
    display: inline-block;
    word-break: break-all;
    pre {
      overflow: auto;
      &::-webkit-scrollbar {
        width: 4px;
        height: 4px;
      }
    }
    img {
      max-width: 100%;
    }
  }
  .avatar {
    width: 31px;
    height: 31px;
    border-radius: 50%;
  }
}

.kefu {
  justify-content: center;
  border-radius: 8px;
  background-color: white;
  padding: 12px 16px;
  margin: 0 auto;
  margin-bottom: 20px;
  width: 260px;
}

.preabnormal {
  text-indent: 0;
  .message {
    padding: 10px;
  }
  .ops {
    display: flex;
    flex-direction: row;
    align-items: center;
    color: #f59f49;
    .tip {
      width: 100px;
      height: 30px;
      background-image: url('../../../assets/img/preabnormal.png');
      background-size: 100% 100%;
    }
  }
}

.user {
  flex-direction: row-reverse;
  .message {
    background-color: #fff;
    padding: 12px 16px;
    border-radius: 8px;
    margin-right: 17px;
  }
  .avatar {
    margin-top: 8px;
  }
}

.bot {
  flex-direction: column;
  audio {
    width: 0;
    height: 0;
    opacity: 0;
  }
  .AI {
    display: flex;
    flex-direction: row;
    align-items: center;
    span {
      margin-left: 10px;
    }
    .loadwrap {
      display: flex;
      .loader {
        display: flex;
        transition: all 0.4s;
        align-items: center;
      }
      .loader div {
        margin-left: 4px;
        background-color: #b6d9f2;
        border-radius: 100%;
        height: 10px;
        width: 10px;
      }
      .box-load1 {
        animation: brighten 1.2s infinite;
      }
      .box-load2 {
        animation: brighten 1.2s infinite;
        animation-delay: 0.3s;
      }
      .box-load3 {
        animation: brighten 1.2s infinite;
        animation-delay: 0.6s;
      }
    }
  }
  .message {
    margin-top: 10px;
    margin-left: 30px;
    padding: 10px;
    width: 692px;
    .tips {
      color: #999;
      font-size: 14px;
      margin-top: 10px;
    }
    .quote {
      color: #999;
      margin-top: 10px;
      .refs {
        display: flex;
        flex-direction: column;
        .t {
          background-color: #f4f5f7;
          color: #333333;
          margin-bottom: 10px;
        }
        span {
          line-height: 1.4;
          color: #1989fa;
          padding: 5px 10px;
          cursor: pointer;
        }
      }
    }
    .upload {
      position: relative;
      margin-top: 10px;
      .upload-wrap {
        width: 40px;
        height: 40px;
        position: relative;
        background-image: url('../../../assets/img/add.png');
        background-size: 100% 100%;
        border-radius: 8px;
        input {
          position: absolute;
          width: 40px;
          height: 40px;
          top: 0;
          left: 0;
          opacity: 0;
        }
      }
      .list {
        width: 100%;
        background-color: transparent;
        display: flex;
        flex-wrap: wrap;
        img {
          width: 40px;
          height: 40px;
          margin-right: 10px;
          margin-bottom: 10px;
        }
      }
    }
    .bottom {
      margin-top: 10px;
      color: #999;
      border-top: 1px dashed #eee;
      text-align: center;
      display: flex;
      flex-direction: row-reverse;
      position: relative;
      padding-top: 10px;
      .MP3_btn {
        position: absolute;
        width: 30px;
        height: 30px;
        left: 10px;
        top: 16px;
        cursor: pointer;
      }
      .play {
        background-image: url('../../../assets/img/play.png');
        background-size: 100% 100%;
      }
      .pause {
        background-image: url('../../../assets/img/pause.png');
        background-size: 100% 100%;
      }
      .title {
        padding: 10px 0;
      }
      .ops {
        display: flex;
        align-items: center;
        justify-content: center;
        color: #333333;
        padding: 10px;
        .dash {
          width: 1px;
          height: 100%;
          border-left: 1px dashed #eee;
          margin: 0 10px;
        }
        div {
          display: flex;
          align-items: center;
          justify-content: space-between;
          cursor: pointer;
          img {
            width: 20px;
            height: 20px;
            margin-right: 10px;
          }
          .like {
            animation: like-animation 0.5s ease-in-out;
          }
          .unlike {
            animation: unlike-animation 0.5s ease-in-out;
          }
        }
      }
    }
  }
  .btns {
    margin-top: 10px;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: flex-start;
    .btn {
      width: 50%;
      border: 1px solid #dcdee0;
      padding: 8px 16px;
      border-radius: 20px;
      color: #222;
      text-align: left;
      margin-bottom: 10px;
      position: relative;
      cursor: pointer;
      .next {
        position: absolute;
        width: 10px;
        height: 18px;
        top: 10px;
        right: 30px;
        background-image: url('../../../assets/img/next.png');
        background-size: 100% 100%;
      }
    }
  }

  .table {
    padding: 10px 0;
    width: 100%;
    overflow-x: auto;
    &::-webkit-scrollbar {
      width: 4px;
      height: 8px;
    }
    /* 设置表格的基本样式 */
    table {
      min-width: 100%;
      border-collapse: collapse; /* 边框合并 */
      border-radius: 5px 5px 0 0; /* 圆角边框 */
      overflow: hidden;
    }
    /* 设置表头的样式 */
    th {
      color: #222;
      padding: 10px;
      text-align: center;
      border-width: 1px;
      border-style: solid;
      border-color: #979797;
      font-weight: normal;
      white-space: nowrap;
    }
    tr {
      border-width: 1px;
      border-style: solid;
      border-color: #979797;
    }
    td {
      color: #222;
      padding: 10px;
      border-width: 1px;
      border-style: solid;
      border-color: #979797;
      text-align: center;
      white-space: nowrap;
    }
  }
  .funcs {
    display: flex;
    flex-direction: column;
    .suggest {
      margin: 10px 0;
      font-weight: 600;
    }
    .func {
      width: 50%;
      padding: 8px 16px;
      background-color: #fff9f2;
      border-radius: 20px;
      color: #333333;
      display: flex;
      justify-content: flex-start;
      align-items: center;
      cursor: pointer;
      margin-bottom: 8px;
      .title {
        flex: 1;
        position: relative;
        .next {
          position: absolute;
          width: 10px;
          height: 18px;
          top: 2px;
          right: 10px;
          background-image: url('../../../assets/img/next.png');
          background-size: 100% 100%;
        }
      }
      img {
        width: 20px;
        height: 20px;
        margin-right: 10px;
      }
    }
  }
}

.message-wrap:last-child {
  margin-bottom: 0;
}
</style>
